iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0

前言

Day 14 我們整理了第一份 Agent 評測報告。

到目前為止,平台已經可以做到:

evals/cases.json
  -> batch runner
  -> SimpleAgent
  -> trace logging
  -> evaluator
  -> eval_run_*.json

這表示 Trace 和 Eval 的基本流程已經跑通。

但 Day 14 也看到了目前最大的限制:

我們還在使用 FakeLLMClient。

FakeLLMClient 很適合在前期建立平台,因為它不需要 API key、不需要網路,也不會有額外成本。

不過它畢竟不是真正的 LLM。

所以從今天開始,我們要把 Agent 從 fake client 接到 real LLM。

本篇會使用 Gemini API,並以 Gemini Flash 作為主要模型。


今天要完成什麼?

今天要做到的是:

在不大改 SimpleAgent 的前提下,新增一個可以呼叫 Gemini API 的 LLM client。

會完成:

  1. 安裝 Gemini Python SDK。
  2. 設定 GEMINI_API_KEY。
  3. 新增 agents/gemini_llm.py。
  4. 新增 agents/client_factory.py,讓系統可以切換 fake / Gemini。
  5. 修改 app.py,讓手動執行可以選擇 LLM provider。
  6. 修改 evals/runner.py,讓 batch evaluation 可以跑 Gemini baseline。
  7. 比較 FakeLLMClient 和 GeminiLLMClient 的差異。

今天先不做:

  • Gemini tool calling。
  • JSON schema retry。
  • 多模型比較。
  • prompt A/B testing。
  • failure type 自動分類。

今天的重點很單純:

先把真正的 LLM 接進既有平台,讓後面的評測結果更接近真實 Agent 行為。


為什麼 Day 15 才接真正的 LLM?

一開始不直接接 LLM,是刻意的設計。

如果 Day 2 就直接串 Gemini API,讀者可能會同時遇到很多問題:

  • API key 怎麼設定。
  • SDK 怎麼安裝。
  • 模型名稱怎麼選。
  • 呼叫失敗怎麼處理。
  • 回傳格式怎麼接到 Agent。
  • 測試結果不穩定要怎麼解讀。

這會讓文章焦點變得很散。

所以前兩週先用 FakeLLMClient 建立穩定的工程骨架:

Agent Runner
Trace
SQLite
Trace Viewer
Eval Dataset
Batch Runner
Evaluator
Baseline Report

等平台骨架完成後,再接真正的 LLM。

這樣一來,我們可以確認今天的改動只集中在 LLM client 替換,而不是整個系統一起重寫。


今天的專案結構

今天會新增兩個檔案,並修改兩個檔案。

agent-testing-platform/
  app.py
  agents/
    __init__.py
    fake_llm.py
    gemini_llm.py
    client_factory.py
    simple_agent.py
  evals/
    __init__.py
    cases.json
    runner.py
    evaluators.py

今天新增:

檔案 用途
agents/gemini_llm.py 呼叫 Gemini API 的 LLM client
agents/client_factory.py 根據環境變數建立 fake 或 Gemini client

今天修改:

檔案 修改內容
app.py 改用 create_llm_client() 建立 LLM client
evals/runner.py 讓 batch evaluation 可以切換 LLM provider

安裝 Gemini Python SDK

Gemini API 的官方 Python SDK 是 google-genai。

在 agent-testing-platform/ 專案根目錄安裝:

python3 -m pip install google-genai

如果你有使用 virtual environment,請先啟動環境再安裝。

例如:

python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install google-genai

安裝完成後,可以用以下指令確認:

python3 -m pip show google-genai

設定 Gemini API key

Gemini API 需要 API key。

這類 key 不應該寫死在程式碼裡,也不應該 commit 到 Git repository。

今天先用環境變數設定。

在終端機執行:

export GEMINI_API_KEY="你的 Gemini API key"

接著設定模型名稱:

export GEMINI_MODEL="gemini-3.6-flash"

這裡使用 Flash 系列,是因為它通常適合做教學專案的第一個 real LLM baseline:

  • 速度快。
  • 成本相對適合實驗。
  • 足夠處理一般問答、格式輸出與指令遵循任務。

模型名稱會隨官方更新而改變。

如果執行時遇到 model not found,請到 Gemini API 的 Models 文件確認你帳號目前可用的 Flash 模型,並調整 GEMINI_MODEL。


建立 GeminiLLMClient

前面我們已經讓 SimpleAgent 依賴一個抽象的 LLM client。

也就是說,SimpleAgent 不需要知道背後是:

  • fake client
  • Gemini
  • OpenAI
  • Claude
  • local model

它只需要知道這個 client 有 chat(messages) 方法。

今天就利用這個設計新增 Gemini 版本。

新增 agents/gemini_llm.py:

import os

from google import genai


class GeminiLLMClient:
    def __init__(self, model: str | None = None):
        api_key = os.getenv("GEMINI_API_KEY")

        if not api_key:
            raise ValueError("GEMINI_API_KEY is not set")

        self.client = genai.Client(api_key=api_key)
        self.model = model or os.getenv("GEMINI_MODEL", "gemini-3.6-flash")

    def chat(self, messages: list[dict]) -> dict:
        prompt = self._messages_to_prompt(messages)

        interaction = self.client.interactions.create(
            model=self.model,
            input=prompt,
        )

        return {
            "type": "final_answer",
            "content": interaction.output_text or "",
        }

    def _messages_to_prompt(self, messages: list[dict]) -> str:
        parts = []

        for message in messages:
            role = message["role"].upper()
            content = message["content"]
            parts.append(f"{role}:\n{content}")

        return "\n\n".join(parts)

這個檔案的重點有三個。

第一,從環境變數讀取 GEMINI_API_KEY。

如果沒有設定,就直接丟出錯誤:

raise ValueError("GEMINI_API_KEY is not set")

這樣比讓程式在 SDK 內部失敗更清楚。

第二,從環境變數讀取 GEMINI_MODEL。

如果沒有設定,就使用預設值:

gemini-3.6-flash

第三,chat() 回傳格式仍然維持我們自己的 Agent 內部格式:

{
    "type": "final_answer",
    "content": interaction.output_text or "",
}

這點很重要。

因為 SimpleAgent 目前已經認得這種格式:

if response["type"] == "final_answer":
    return AgentResult(answer=response["content"])

所以新增 Gemini client 時,不需要大改 SimpleAgent。


為什麼今天不做 Gemini tool calling?

你可能會注意到,GeminiLLMClient 目前只回傳:

"type": "final_answer"

它不會回傳:

"type": "tool_call"

也就是說,今天接上 Gemini 後,計算題可能會變成由 Gemini 直接回答,而不是呼叫我們 Day 3 實作的 calculator tool。

這是刻意的取捨。

今天如果同時做 Gemini tool calling,會多出不少新問題:

  • Gemini API 的 tool schema 要怎麼定義。
  • 模型回傳 function call 後要怎麼解析。
  • tool result 要怎麼送回模型。
  • trace 要怎麼記錄多輪 tool calling。
  • evaluator 要怎麼區分模型直接回答和工具回答。

這些都值得做,但不是 Day 15 的主要目標。

今天先完成最小整合:

SimpleAgent -> GeminiLLMClient -> Gemini Interactions API -> final answer

Tool calling 可以放到後面的延伸功能。


建立 LLM Client Factory

接下來要讓系統可以選擇使用 fake 或 Gemini。

如果我們直接在 app.py 和 evals/runner.py 到處寫:

GeminiLLMClient()

以後要切換 provider 會很麻煩。

所以今天新增一個小型 factory。

新增 agents/client_factory.py:

import os

from agents.fake_llm import FakeLLMClient
from agents.gemini_llm import GeminiLLMClient


def create_llm_client():
    provider = os.getenv("LLM_PROVIDER", "fake").lower()

    if provider == "fake":
        return FakeLLMClient()

    if provider == "gemini":
        return GeminiLLMClient()

    raise ValueError(f"Unknown LLM provider: {provider}")

這個檔案負責根據 LLM_PROVIDER 建立對應 client。

目前支援兩種:

LLM_PROVIDER 使用的 client
fake FakeLLMClient
gemini GeminiLLMClient

如果沒有設定 LLM_PROVIDER,預設仍然使用:

fake

這樣可以保留前兩週的行為。

也就是說,原本的執行方式仍然有效:

python3 app.py
python3 -m evals.runner

只有想使用 Gemini 時,才需要額外指定:

LLM_PROVIDER=gemini python3 -m evals.runner

修改 app.py

接著讓手動測試也可以使用 factory。

修改 app.py:

from agents.client_factory import create_llm_client
from agents.simple_agent import SimpleAgent


def main():
    user_task = input("Task: ")

    agent = SimpleAgent(llm_client=create_llm_client())
    result = agent.run(user_task)

    print("Answer:", result.answer)

    if result.tool_calls:
        print("Tool calls:")
        for tool_call in result.tool_calls:
            print(f"- tool_name: {tool_call.tool_name}")
            print(f"  tool_input: {tool_call.tool_input}")
            print(f"  tool_output: {tool_call.tool_output}")


if __name__ == "__main__":
    main()

這段修改的重點是:

agent = SimpleAgent(llm_client=create_llm_client())

以前是直接寫死:

agent = SimpleAgent(llm_client=FakeLLMClient())

現在則交給 create_llm_client() 決定。

手動測 fake client:

python3 app.py

手動測 Gemini:

LLM_PROVIDER=gemini python3 app.py

修改 evals/runner.py

接著讓 batch evaluation 也支援 Gemini。

目前 evals/runner.py 裡可能有這兩行:

from agents.fake_llm import FakeLLMClient
from agents.simple_agent import SimpleAgent

修改 evals/runner.py,把 FakeLLMClient import 換成 create_llm_client:

from agents.client_factory import create_llm_client
from agents.simple_agent import SimpleAgent

接著找到建立 Agent 的地方。

原本可能是:

agent = SimpleAgent(llm_client=FakeLLMClient())

修改 evals/runner.py:

agent = SimpleAgent(llm_client=create_llm_client())

完整概念會變成:

def run_evaluation() -> dict[str, Any]:
    init_db()

    agent = SimpleAgent(llm_client=create_llm_client())
    cases = load_cases()
    run_id = create_run_id()
    results = []

    for test_case in cases:
        ...

這樣 runner 不需要知道目前用的是 fake 還是 Gemini。

它只負責:

讀取 cases -> 呼叫 agent -> 評分 -> 輸出結果

LLM provider 的選擇交給 agents/client_factory.py。


執行 Fake Baseline

先確認前兩週的 fake baseline 仍然可以跑。

在 agent-testing-platform/ 專案根目錄執行:

python3 -m evals.runner

因為沒有設定 LLM_PROVIDER,所以預設會使用:

fake

你應該會得到和 Day 14 類似的結果。

例如:

Total cases: 15
Passed: 5
Failed: 10
Success rate: 33.3%

實際數字會依照你目前程式碼和 test cases 稍微不同。

重點是:原本流程不應該因為今天新增 Gemini 而壞掉。


執行 Gemini Baseline

接著執行 Gemini 版本。

確認你已經設定:

export GEMINI_API_KEY="你的 Gemini API key"
export GEMINI_MODEL="gemini-3.6-flash"

然後執行:

LLM_PROVIDER=gemini python3 -m evals.runner

這次 runner 會走:

evals.runner
  -> create_llm_client()
  -> GeminiLLMClient
  -> Gemini API
  -> SimpleAgent
  -> evaluator

執行完成後,同樣會在 data/eval_runs/ 產生結果檔。

例如:

data/eval_runs/eval_run_20260906_153000.json

可以用以下指令檢查:

python3 -m json.tool data/eval_runs/eval_run_20260906_153000.json

檔名請換成你實際產生的檔名。


Gemini Baseline 可能會看到什麼?

接上 Gemini 後,結果通常會比 fake client 更接近真實任務。

例如原本 fake client 可能會回答:

Fake response for: 請回答 HTTP 狀態碼 404 通常代表什麼

Gemini 比較可能回答:

HTTP 狀態碼 404 通常代表找不到請求的資源。

這樣 contains evaluator 就能判定通過,因為答案包含:

找不到

不過,不是每一題都一定會通過。

例如 exact_match 題目:

{
  "input": "請只回覆 OK",
  "expected": "OK",
  "grading_method": "exact_match"
}

模型可能回覆:

OK

也可能回覆:

OK。

甚至可能回覆:

好的,OK。

對人來說這些都很接近,但對 exact_match evaluator 來說,只有完全等於 OK 才會通過。

這就是 Agent 評測會遇到的真實問題:

模型能力變強後,測試不一定全部通過,因為格式遵循、評分方式與任務定義仍然會影響結果。


比較 Fake 與 Gemini 的意義

今天不是只為了把成功率提高。

更重要的是,我們現在有兩種 baseline。

第一種是 fake baseline:

FakeLLMClient + eval dataset

它的用途是確認平台流程穩定。

例如:

  • runner 是否能跑完整組 cases。
  • trace 是否能保存。
  • evaluator 是否能產生 pass / fail。
  • JSON 結果是否能輸出。

第二種是 Gemini baseline:

GeminiLLMClient + eval dataset

它的用途是觀察真實 LLM 的表現。

例如:

  • 知識問答是否改善。
  • instruction following 是否穩定。
  • JSON output 是否符合格式。
  • 哪些失敗來自模型,哪些失敗來自 evaluator 太嚴格。

這兩種 baseline 都有價值。

fake baseline 幫助我們測平台,Gemini baseline 幫助我們測 Agent 行為。


常見錯誤

1. GEMINI_API_KEY is not set

如果看到:

ValueError: GEMINI_API_KEY is not set

代表目前終端機沒有設定 API key。

請重新執行:

export GEMINI_API_KEY="你的 Gemini API key"

注意:這個設定只會存在目前的終端機 session。

如果你關掉終端機後重開,需要重新設定。


2. No module named 'google'

如果看到:

ModuleNotFoundError: No module named 'google'

通常代表還沒安裝 SDK,或是安裝在不同 Python 環境。

請確認:

python3 -m pip show google-genai

如果沒有結果,重新安裝:

python3 -m pip install google-genai

3. model not found

如果看到 model not found,通常代表 GEMINI_MODEL 指定的模型名稱不可用。

請改用你帳號目前可用的 Flash 模型。

例如:

export GEMINI_MODEL="你的可用 Flash 模型名稱"

模型名稱要以 Gemini API 官方 Models 頁面為準。


今天的重點整理

今天我們把 Agent 從 fake client 接到真正的 Gemini API。

完成的內容包含:

  • 安裝 google-genai。
  • 使用 GEMINI_API_KEY 管理 API key。
  • 新增 agents/gemini_llm.py。
  • 新增 agents/client_factory.py。
  • 修改 app.py,讓手動測試可以切換 provider。
  • 修改 evals/runner.py,讓 batch evaluation 可以跑 Gemini baseline。
  • 保留 FakeLLMClient 作為平台流程測試用 baseline。

今天完成後,系統多了一個重要能力:

同一套 Agent 測試平台,可以測 fake client,也可以測真正的 LLM。

從 Day 15 開始,我們的 evaluation result 會更接近真實 Agent 開發會遇到的問題。


下一步

接上 Gemini 後,我們會開始看到更真實的失敗案例。

有些失敗可能是:

  • 答案錯誤。
  • 輸出格式不符合要求。
  • 指令遵循不精確。
  • evaluator 太簡單造成誤判。
  • API 呼叫失敗或 timeout。

Day 16 會正式進入 Failure Analysis。

下一篇會定義 Agent 常見失敗類型,並把 failure_type 加進 evaluation result。

這樣平台就不只會告訴我們:

這一題 failed。

而是能進一步告訴我們:

這一題是 format_error。
這一題是 wrong_answer。
這一題是 instruction_error。

這會讓後續的 dashboard、prompt A/B testing 和 retry 策略更有依據。


參考資料


上一篇
Day 14|第二週回顧:第一份 Agent 評測報告
下一篇
Day 16|定義 Agent 失敗類型,並加入評測結果
系列文
從黑盒到可驗證:30 天打造 AI Agent 的 Trace、Eval 與 Guardrails 系統 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言